if GS_RAILS_SKINS==nil rawset(_G,"GS_RAILS_SKINS",{}) end --Make sure your own lump has this too!

local RAIL = GS_RAILS if RAIL and RAIL.blockload return end --Don't include this part in your own file
if GS_RAILS_SKINS["adventuresonic"] return end --Don't include this line either

--Adventure Sonic! The first time Grind Rails were introduced were in Sonic Adventure 2,
--and he's a true natural on the rails in SRB2 as well! https://mb.srb2.org/addons/adventure-sonic-v1-8b.3589/

GS_RAILS_SKINS["adventuresonic"] = {} --Set up the table
local SKIN = GS_RAILS_SKINS["adventuresonic"] --Make a shortcut to the table for convenience

--This is how you add your character's grinding stats, similar to an S_SKIN!
--You don't need to put in redundant info like I did with some values.
SKIN["S_SKIN"] = {
SIDEHOP = 44, --SA2 had a quite fast sidehop!
SIDEFLIP = 7777, --7777 lets you hold Toss Flag to sideflip. BUT, this requires SPR2_SPNG as well as SPR2_FALL sprites!

AIRDRAG = 0, --SA-Sonic has his own airdrag system.
CROUCH = true,
CROUCHBUTTON = BT_SPIN|BT_CUSTOM2,
MAXSPEED = 0,
SLOPEMULT = 10, --More affected by slopes than most.
AUTOBALANCE = false,
HEROESTWIST = 15,
TWISTBUTTON = BT_SPIN|BT_CUSTOM2,
ACCELCAP = 220, --For Super Sonic. Cannot self-accelerate normally.

STARTMODIFIER = -1,
WALLCLINGSPRITE = SPR2_TAL7, --Special walljump sprite by Metalwario64!
}

--This affects you right before you jump off a rail.
--If RAMPJUMP is true,you just successfully did a jump off an SA2-style rail ramp!
--if RAMPJUMP is "frontiers", you're doing a Sonic Frontiers Style Jump off the rail. (No air drag to slow you down)
--If this function returns true, it overrides all jump behaviour. (This also means dislodging from the rail itself, etc!)
--If this function returns "customramp", it completely overrides the jump behaviour, but only if you did an SA2-style rampjump.
--If this function returns "norampjump", SA2-style rampjumps and Frontiers jumps are forced to use regular jump behaviour.
SKIN["CustomJump"] = function(p, s, GS, rail, RAMPJUMP)
	if not (p.yusonictable) return end --
	local yu = p.yusonictable
	yu.framejump,yu.airborne = 0,0
	yu.sa2jumpdone = false

	local LOOP = RAIL.LearnLoopType(GS.LastRail or GS.myrail)
	if LOOP and (((LOOP.jump or 0) < 0) or LOOP.walljump)
		yu.noairdrag = max(yu.noairdrag or 0, 22)
		GS.airdrag = nil
	end
	if (RAMPJUMP)
		if RAMPJUMP==true --Btw, you can also check if GS.JumpRamp is currently an active table for SA2 rampjumps!
			--
		elseif RAMPJUMP=="frontiers" --You can also check if GS.FrontiersLeap is currently ticking down.
			--
		end	
	end
end

--This affects you right before you sidehop off a rail. SIDEJUMP is -1 when jumping left, or 1 when jumping right!
--If this returns true, it overrides all sidehop behaviour.
--If this returns "nostretch", it won't add stretch effects.
--If this returns "hanghop", GS.hanghop is set to your STAT.SIDEHOP value.(or 13) Intended for hangrail-specific sidehops.
--RAMPJUMP is usually irrelevant here, unless you feel like doing some nutty shenanigans.
SKIN["CustomSideHop"] = function(p, s, GS, rail, SIDEJUMP, RAMPJUMP)
	if not (p.yusonictable) return end --
	local yu = p.yusonictable
	yu.sa2jumpdone,yu.regainjump = true, false
	yu.airborne = 99
	yu.framejump = 0
end

--This hook plays right as a player tries to use the emergency dislodge with Custom3.
--If this returns true, it overrides all dislodge behaviour.
SKIN["CustomDislodge"] = function(p, s, GS,rail)
	if not (p.yusonictable) return end --
	local yu = p.yusonictable
	yu.sa2jumpdone = false
	yu.framejump,yu.airborne = 0,0
end

--This affects players right before you leave a rail by any means!
--If this function returns true, it'll completely override the entire exit thinker. Don't do that too recklessly, though.
SKIN["PreExit"] = function(p,s,GS, rail, METHOD)
	if not (p.yusonictable) return end --
	local yu = p.yusonictable local YUSO = YuSonic
	if YUSO
		if yu.homingtrick
			YUSO.YuCancelTricks(p, s, yu)
		end
		if yu.lsacharged
			YuCancelLSA(p, s, yu)
		end
		if yu.lsadash
			YuStopLSAdash(p, s, yu)
		end
		if yu.lsaattack or yu.lsahoming or yu.lsadash
			YUSO.YuStopLSA(p, s, yu)
		end
		if yu.bouncestate
			YuResetBounce(p, s, yu)
		end
		if p.YuRushMode
			if (p.cmd.buttons & BT_SPIN)
				yu.spin = max($, 3)
				if not (p.YuRushMode.boosting)
					if GS.boosting
						p.YuRushMode.boosting = 30
					else
						yu.spin = 999<<16
						YUSO.CantBoost(p, 2)
					end
				end
			end
		end
	end
	if (METHOD) or rail and rail.GSVertRail or (GS.failroll) or P_PlayerInPain(p)
		if (p.cmd.buttons & BT_JUMP) and (yu.jump < 2)
			yu.jump = 2
		end
		yu.framejump = 0
	end
	p.pflags = $|PF_JUMPDOWN|PF_USEDOWN & ~PF_STARTJUMP
	yu.homingtrick,yu.lsatrickanim,yu.airborne = 0,0,0
	yu.sa2jumpdone = false	
end

--Right after you've flung off a rail by any means, but still before "PostExit".
--This hook is mostly useful if you want to alter fling physics for your character.
--If METHOD is "sidehop", you just did a sidejump.
--if METHOD is "jump", you manually did a jump.
--if METHOD is anything else, assume the player was flung off the rail by other means. (Reached the end/got hurt/whatever)
--SLOPEFLING is your vertical slope momentum, if any.
SKIN["PostFling"] = function(p,s,GS,rail, METHOD, SLOPEFLING)
	local yu = p.yusonictable if not yu return end 
	S_StopSoundByID(s, sfx_yusfx4) S_StopSoundByID(s, sfx_yusfxi)
	
	local yu,RB = p.yusonictable,p.YuRushMode 
	if RB and ((SLOPEFLING or 0) > 10*s.scale) and (s.momz*P_MobjFlip(s) > 10*s.scale) 
		if not (p.pflags & (PF_STARTJUMP|PF_JUMPED|PF_SPINNING)) and (METHOD!="sidehop") and (METHOD!="jump")
			s.eflags = $|MFE_SPRUNG
			RB.trickready = true 
			if YuSonic 
				YuSonic.LaunchTricks(p, s, yu, RB)
			end
		end
	end
	if p.powers[pw_super]
		if (abs(GS.railspeed) > 115*s.scale) and not (p.YuRushMode) --Give an instant Blue Bolt with enough speed!
			if GS.blueboltcd
				yu.superaura,yu.superaurabuild = 7,0
			else
				yu.superaurabuild = 20
			end
			s.momx = FixedDiv(FixedMul($, 90), 84)
			if not ((s.flags2 & MF2_TWOD) or twodlevel)
				s.momy = FixedDiv(FixedMul($, 90), 84)
			end
		end
	end
	yu.framejump,yu.jump = 0,99
	GS.blueboltcd = 0
end


--This takes place after everything else once you've exited a rail by any means.
--Explanations for "PostFling" apply here too.
SKIN["PostExit"] = function(p, s, GS, rail, METHOD, SLOPEFLING, JUMPRAMP)
	local yu = p.yusonictable if not yu return end
	GS.airdrag,GS.YuSpin = nil
	yu.custom3 = 99<<16
	if GS.leftrail==3 or P_IsObjectOnGround(s) or GS.LTAB and GS.LTAB.nojumpbuffer and GS.LTAB.snappedtofloor
		if (p.cmd.buttons & BT_SPIN) and (GS.backupspin < 22)
		and not (p.pflags & (PF_STARTJUMP|PF_JUMPED|PF_SPINNING|PF_THOKKED|PF_FULLSTASIS))
			yu.spin = 0
		end
	else
		yu.jump,yu.spin= 99<<16,99<<16	
	end
	if not yu.ledgegrabcooldown
		yu.ledgegrabcooldown,yu.ledgeupangle = 7, nil
	end
	if GS.LTAB and (GS.LTAB.snappedtofloor or GS.LTAB.downjump or GS.LTAB.nojumpbuffer)
	or JUMPRAMP==2 or GS.walljump and GS.walljump.timer
		yu.jump = 99<<16	
		yu.framejump = 0
	end
	if JUMPRAMP
		if JUMPRAMP==2 --Frontiers leap?
			yu.noairdrag,yu.noairdrag2 = max(yu.noairdrag or 0, 64),36
			if YuSonic YuSonic.SAFlipJump(p,s,yu,"start") end
			yu.lasttrick = 3
		elseif JUMPRAMP==1 or JUMPRAMP==-1 --SA2 rampjump?
			S_StartSound(s, sfx_yusfxc)	
			yu.homingtrick,yu.lsatrickanim = 0,0
			yu.airborne = 0
			yu.sa2jumpdone = false
			YuVoice(p, s, yu, (yu.supersonic) and sfx_yuvoc9 or sfx_yuvocs, true)
		end
	end
	if (p.cmd.buttons & BT_SPIN)
		local LOOP = RAIL.LearnLoopType(GS.lastrail or rail)
		if LOOP and LOOP.jump!=-1
			yu.spin = 1
			p.cmd.buttons = $ & ~BT_SPIN
		end
	end	
end

--The PreGrindThinker plays right before the Grinding behavior runs! "rail" and "GS" are guaranteed valid here.
--This is the best hook for making quick adjustments right before the action, or editing input or Rail Button data! 
--Do note that "PlayerThink" technically still runs before this!
SKIN["PreGrindThinker"] = function(p, s, GS, rail)
	if p.YuRushMode and GS
		local RB = p.YuRushMode
		if not (p.cmd.buttons & BT_CUSTOM2) and RB.boostmeter
			GS.twistbutton = 0 --Disable twist button when boost is available, unless pressing custom2.
		end
	end
end

--Executes when your character attempts to do a Twist Drive, that Sonic Heroes hop thingy when you spin in circles.
--If this function returns true, it'll skip the default Heroes Hop behaviour entirely.
--SKIN["TwistDrive"] = function(p, s, GS, rail)
--	
--end

local function EndBoost(p,s,yu,RB) 
	RB.boosting,yu.noairdrag = 0,0
	p.charflags,p.powers[pw_strong] = $ & ~SF_RUNONWATER, $ & ~(STR_HEAVY|STR_WALL)
end
local function RushGhost(s, COLOR, FUSE)
	local ghs = P_SpawnGhostMobj(s) 
	if COLOR
		ghs.color = COLOR --No colorizing here.
	else
		ghs.color,ghs.colorized = (s.player and s.player.powers[pw_super]) and s.color or SKINCOLOR_WHITE, true
	end
	ghs.scale = s.scale
	ghs.fuse,ghs.blendmode = (FUSE) or 6,AST_ADD
	if leveltime % 2 == 0
		ghs.frame = ($ & ~FF_TRANSMASK)|FF_TRANS50
		ghs.momx,ghs.momy,ghs.momz = s.momx/2,s.momy/2,s.momz/4
	elseif leveltime % 3 == 0
		ghs.momx,ghs.momy,ghs.momz = s.momx/4,s.momy/4,s.momz/4	
		ghs.spritexscale,ghs.spriteyscale = $+15000,$+15000
	end return ghs --
end

--This Thinker plays every frame exclusively while you are grinding, right AFTER the grind behaviour took place.
--This is likely what you'll want to use for custom abilities like boosting on rails, unless some visuals are desynching.
--In that case, try using a FinalThinker instead. 
--NOTE: "rail" is not always valid here.
SKIN["GrindThinker"] = function(p, s, GS, rail)
	local yu = p.yusonictable if not (yu) return end --
	local RB = p.YuRushMode
	yu.framejump = 0
	yu.jump = 99<<16
	--Blue bolt while grinding?
	if p.powers[pw_super]
		if RB
			yu.superaura,yu.superaurabuild = 0,0
		elseif (abs(GS.railspeed) > 115*s.scale) and not (LOOP and LOOP.walljump)
			if GS.blueboltcd 
				yu.superaura,yu.superaurabuild = 7,0
			else
				yu.superaurabuild = 20
			end
			p.speed = max($, abs(GS.railspeed or 0))
			if (p.cmd.forwardmove > 10)  --Blue bolt on rails?!
				if (abs(GS.railspeed) < 236*s.scale)
					GS.railspeed = FixedDiv(FixedMul($, 161), 160)
				end
				P_YuQuake(p, abs(GS.railspeed)/20, 2)
			end			
			if not GS.blueboltcd 
				yu.hudboost = 1
				S_StartSound(s, sfx_yusfx7)
				GS.blueboltcd = 20
				if not yu.noflashes	
					local boom
					for d = 1,5						
						boom = P_SpawnMobjFromMobj(s,0,0,0, MT_YUINSTASHIELD)
						boom.scalespeed = $*(d*3)
						boom.tics = $+d
						P_InstaThrust(boom, GS.grinddirection, abs(GS.railspeed*2/3))
						boom.momz = ((((s.z-yu.lastz) or 1)/d) or 1)/2
						boom.colorized = true
						boom.color = SKINCOLOR_SUPERGOLD5-d+1
						boom.destscale = s.scale*99
						boom.blendmode = AST_ADD
						boom.flags = $|MF_NOCLIPHEIGHT|MF_NOCLIPTHING
					end		
				end
			end
		elseif (p.cmd.forwardmove > 10) and not (LOOP and LOOP.walljump) --You can accelerate on rails!
			GS.railboost = $+34000
		end
	elseif GS.perfectcorner==6 and GS.ANGswap and ((GS.ANGswap.init or 0) < 4) and not (GS.flipanim) 
		if RB and GS.crouchgrind and RB.boostmeter!=nil and (RB.boostmeter < 2000) --From a perfect corner, gain this!
			S_StartSoundAtVolume(nil, sfx_yusfxk, 50)
			RB.boostmeter = $+(($ < 1000) and 80 or ($ < 2000) and 50 or 25)
		end
	end
end

--This Thinker plays before everything else, except the PreThinkFrames, regardless of whether you're on a rail or not.
--If this function returns true, it'll overwrite EVERYTHING. 
--Only try that if you're as advanced at Lua as you are desperate!
--NOTE: "rail" is very rarely not always valid here.
SKIN["PlayerThink"] = function(p, s, GS, rail)
	local yu = p.yusonictable if not yu return end
	if GS.FrontiersLeap
		yu.noairdrag = max($, 5) --Oh boy...SA-Sonic without airdrag.
	elseif GS.JumpRamp
		local RAMP,INIT = GS.JumpRamp.timer or 0, GS.JumpRamp.init or 0
		if (INIT < 17) and not P_IsObjectOnGround(s) and not p.powers[pw_carry] and not p.homing
		and (p.pflags & (PF_JUMPED|PF_NOJUMPDAMAGE))
			yu.noairdrag,yu.noairdrag2 = 0,0
			yu.jump,yu.spin = 99<<16,99<<16
		end
	end
	if GS.walljump and GS.walljump.timer and GS.walljump.loopcam
		yu.nosarun4 = max(yu.nosarun4 or 0, 4)	
		yu.noairdrag = max($, 4)
	end
	if GS.LTAB and GS.LTAB.timer
		if yu.bouncestate or yu.farthok
			GS.LTAB.timer = 0
		elseif GS.LTAB.init and (GS.LTAB.init <= 2) and GS.LTAB.snappedtofloor and (abs(GS.LTAB.thrust or 0) > 40*s.scale)
		and ((abs(p.cmd.sidemove) > 24) or (abs(p.cmd.forwardmove) > 24)) and P_IsObjectOnGround(s) --Momentum retaining!
			yu.frictionlock = min(76, max(yu.frictionlock or 0, (GS.LTAB.thrust/(s.scale*3))) )
			yu.runfriction,s.friction,yu.groundfriction = FRACUNIT,FRACUNIT,FRACUNIT
		end
	end	
end	

--This plays right as you attach to a rail during the initial collission, but technically aren't grinding yet.
--Best used for manipulating momentum or angles relative to the rail. 
--If this returns true, you'll negate the rail collission altogether.
SKIN["PreAttach"] = function(p, s, GS, rail)
	if not (p.yusonictable) return end --
	local yu,RB = p.yusonictable,p.YuRushMode
	if RB and RB.boosting
		GS.YU_forceboost = {boosting = RB.boosting, timer = 3}
	else
		GS.YU_forceboost = nil
	end
	if not RB
	or not RB.boosting and not ((yu.spin > 4) and (yu.spin < 99<<16) and (p.cmd.buttons & BT_SPIN))
		GS.boosting = 0
		yu.spin = 999<<16
	elseif GS.boosting and RB
		RB.boosting = max(RB.boosting or 0, GS.boosting)
	end
	if not (yu.ledgegrabcooldown)
		yu.ledgegrabcooldown = 3
	end
	if s.yu_bubblebounce or s.yu_burningthok
		if YuSonic!=nil
			YuSonic.BubbleFlameAbilities(p,s,yu, true)
		end
	end
end

--This plays on the first real frame of grinding, when everything's already been set up properly.
--Best used to add "starting abilities", start a grind with a special property, or turn off unwanted attributes.
SKIN["StartGrind"] = function(p, s, GS, rail)
	if p.yusonictable
		local yu,RB = p.yusonictable,p.YuRushMode
		if yu.firstbounce or yu.bounce
			YuResetBounce(p, s, yu)
		end
		yu.homingsetup = false
		yu.superflight = 0
		yu.prehomecancel,yu.prehomeCAM = nil
		GS.YuSpin = nil
		if RB
			RB.airboost = 0
			RB.kicktricking,RB.kickanim = nil	
			if GS.boosting and RB.boostmeter and (p.cmd.buttons & BT_SPIN)
				RB.boosting = max(RB.boosting or 0, GS.boosting)
			else
				GS.boosting = 0
			end
			yu.spin = 64
		end
	end
end

local TR = {}
TR[SPR2_ROLL]=true
TR[SPR2_CLMB]=true
TR[SPR2_TAL0]=true
TR[SPR2_TAL1]=true
TR[SPR2_TAL2]=true

local SS2 = SPR2F_SUPER if SS2==nil SS2 = FF_SPR2SUPER or 0 end --For 2.2.13 & 2.2.14 cross compatibility.

--This sets a custom animation, called after the GrindThinker.
--If this function return true, it'll ALWAYS go to the PostThinkFrame, overriding all animation behaviour there.
--If this function return false, it'll also always go to the PostThinkFrame, but lets the normal animations play out first.
--Otherwise, it tries to do this before GRND/dunce poses, and those will take priority.
--If the POSTTHINK variable is true, then this was called by a PostThinkFrame thinker.
--The ANIMCALL variable shows GS.animcall's initially value. It can be "sideflip", "flipanim", "force", "forcebutnoskip", or a number.
--If your character has GS.forcesprite set to something, this will also be called.
SKIN["CustomAnim"] = function(p, s, GS, rail, POSTTHINK, ANIMCALL)
	if ANIMCALL=="sideflip" or not p.yusonictable return end
	local yu = p.yusonictable
	
	local SPRITE2 = s.sprite2&~SS2
	
	if (TR[SPRITE2])==true and rail and ((GS.grinding or 0)>1) --Spinstate special behavior?
		GS.YuSpin = GS.YuSpin and $+1 or 1
		local OGYUZ = yu.lastz
		if GS.LastGrindZ!=nil yu.lastz = GS.LastGrindZ end
		local OGX,OGY,OGZ = s.momx,s.momy,s.momz
		P_InstaThrust(s, p.drawangle, abs(GS.railspeed or 0))
		if SPRITE2==SPR2_ROLL or GS.forcesprite==SPR2_ROLL --Spawn Jumpball when in a rolling state and add spintrail for flair.
			YuJumpBallSpawn(p, s, yu)
			if yu.jumpball and yu.jumpball.valid
				P_MoveOrigin(yu.jumpball, s.x, s.y, s.z)
			end	
			if yu.flair=="advance"
				P_SpawnSkidDust(p, 40<<16)
			end
		end
		if (abs(GS.railspeed) > 22*s.scale) and YuSonic
			GS.speedtrail,yu.alreadytrailed,yu.spintrailticket = true, false, nil	
		end
		if GS.YuSpin==1 
			if ((GS.grinding or 0) < 6) or GS.railtossflag and (GS.railtossflag < 3) --Make a sound too, why not.
				local SFX = (yu.flair=="advance") and sfx_yusfxh or (yu.flair=="SA1") and (sfx_yusfxn or sfx_zoom) or sfx_zoom
				if p==displayplayer and not S_SoundPlaying(s, SFX)
					S_StartSoundAtVolume(nil, SFX, 77, p)
				else
					S_StopSoundByID(s, SFX)
				end
				S_StartSound(s, SFX)
			end
		end
		s.momx,s.momy,s.momz = OGX,OGY,OGZ --Revert everything in the end.
		yu.lastz = OGYUZ
	else
		GS.YuSpin = nil
	end
	if p.powers[pw_super] and ((GS.grinding or 0)>1)
		if GS.flipanim
			p.speed = min($, 42*s.scale)
		else
			local SPEED = abs(GS.railspeed or 0)
			if (SPEED >= 54*s.scale)
				p.speed = SPEED
				if YuSonic and yu
					YuSonic.SuperAura(p, s, yu)
				end
			end
		end
	end	
end

--This takes place right after the hanging state was set in the "PlayerThink".
--It's not a PostThinkFrame thing like "CustomAnim", so set GS.forcesprite and GS.animcall if you don't want your hanging
--animation overwritten, or use a "FinalThinker" instead and check for S_PLAY_RIDE and Gs.grinding
--SA Sonic doesn't use this, so I've commented it out to show what it'd look like.
--SKIN["CustomHangAnim"] = function(p, s, GS, rail)	
--	
--end	

--This is basically a "PlayerThink", except called in PostThinkFrame. It's called even when you're not grinding.
--Using this gives you the last say, no matter what. p,s,and GS are ALWAYS valid, but you're not always grinding on a rail.
--This is THE thinker you wanna grab for overlays that seem to be desyncing.
SKIN["FinalThinker"] = function(p, s, GS)
	local yu,RB = p.yusonictable,p.YuRushMode if not yu or not GS return end
	if GS.blueboltcd and (abs(GS.railspeed or 0) < 115*s.scale)
		GS.blueboltcd = max($-1,0)
	end
	if GS.grinding and GS.myrail
		local YUSO = YuSonic if YUSO==nil return end --
		local rail = GS.myrail
		local LOOP = RAIL.LearnLoopType(rail)
		local MX,MY,MZ = s.momx,s.momy,s.momz
		if RB for i = 1,2
			if i==2 
				s.momx,s.momy,s.momz = MX,MY,MZ break --Restore old momentum and move on!
			elseif GS.railspeed!=nil and GS.LastGrindZ!=nil
				P_InstaThrust(s, p.drawangle, GS.railspeed) --This little hack saves me from having to rewrite anything.
				s.momz = s.z-GS.LastGrindZ
			end
			local SPIN = 0 --We have Rush Mode active. Time to do some boosting!
			yu.spin = 64
			if GS.perfectcorner --Gain some boostmeter for good cornering.
				YUSO.BoostGain(RB, 25)
			end
			
			if (p.cmd.buttons & (BT_SPIN|BT_CUSTOM1))
				SPIN = max(GS.railspin, GS.railc1) or 0
				if (SPIN >= 999<<16)
					SPIN = (GS.boosting or RB.boosting) and 2 or 0
				end
			elseif not (GS.grinding and (GS.grinding < 3))
				GS.boosting = 0	
			end
			local SPEED = abs(GS.railspeed or 0) --The speed we're moving at.
			if LOOP and LOOP.walljump
				GS.boosting = 0
				if RB.boosting --Turn off the boost!
					EndBoost(p,s,yu,RB)		
				end
			elseif not RB.boostmeter
				GS.boosting = 0			
				if RB.boosting --Turn off the boost!
					S_StartSoundAtVolume(nil, sfx_antiri, 65, p)
					S_StartSoundAtVolume(nil, sfx_adderr, 85, p)				
					S_StartSound(s, sfx_shattr)
					RB.redflash = 15
					GS.YU_GhostUpKeep = 18
					EndBoost(p,s,yu,RB)
					continue --
				elseif SPIN
					GS.railc1,GS.railspin = 999<<16,999<<16
					if SPIN == 1
						if GS.railspeed and ((GS.grinding or 0) > 4) --Punishment for spamming boost without meter.
							GS.railspeed = $*6/7
						end
						RB.notboosting = 0
						S_StartSound(s, sfx_s236)
						RB.redflash = 15
						GS.YU_GhostUpKeep = 18
						continue --
					end
				end
			else
				if GS.YU_forceboost
					if not (GS.YU_forceboost.timer)
						GS.YU_forceboost = nil
					else
						GS.YU_forceboost.timer = $-1
						RB.boosting = max(RB.boosting or 0, GS.YU_forceboost.boosting or 0)
						GS.boosting = max(GS.boosting or 0, RB.boosting)
						SPIN = max($,2)
					end
				end
				if SPIN 
					GS.cantcrouch = 2 --Doesn't stack.
					GS.crouchgrind = false
					RB.cantboost = 0 --Always allow boosting on the rails so long as you have meter!
					
					if SPIN==1 and not RB.boosting and not GS.boosting
						YUSO.StartBoost(p, s, yu, RB)
					end
					p.powers[pw_strong] = $|STR_HEAVY|STR_WALL  --We're currently boosting.
					if RB.boosting or GS.boosting
						GS.YU_GhostUpKeep = 8
					end		
					RB.boosting = max(GS.boosting or 0, RB.boosting or 0)+1
					GS.boosting = RB.boosting --So the regular behaviour won't turn the boost off.
			
					if (RB.boosting < 5) --Beginning of the boost!
						YUSO.BoostBurst(s, 70000)
						YUSO.BoostBurst2(s)	
						local QUAKEPOWER =  min(80<<16, P_IsObjectOnGround(s) and SPEED/2 or SPEED)
						P_YuQuake(p, QUAKEPOWER, 4)
						if RB.boosting==1 and not ((s.flags2 & MF2_TWOD) or twodlevel)
							YUSO.BoostBurst(s, 79000, nil, nil, FF_TRANS40)
						end
					elseif (SPEED < 15*s.scale) --Way too slow. Cancel boost!
						GS.railspin,GS.railc1 = 99<<16,99<<16 continue --		
					end					
					
					--Now determine boost drain!
					if not RB.overdrive
						local DRAIN = 10
						if (RB.boosting > 35) --Keeping up your boost lowers drain
							DRAIN = (RB.boosting >= 70) and $-2 or $-1
						end
						if (p.rings>29) --Having many rings lower drains.
							DRAIN = (p.rings>119) and $-3 or (p.rings>59) and $-2 or $-1	
						end
						if p.powers[pw_sneakers] --Super Sneakers half the drain.
							DRAIN = $/2
						end
						RB.boostmeter = max($-DRAIN,0)
					end
					
					local TOPSPEED = 82*s.scale --Determine boostspeed!
					local MINSPEED = 46*s.scale
					if yu.supersonic 
						TOPSPEED = (yu.hypersonic) and 112*s.scale or 100*s.scale
						MINSPEED = (yu.hypersonic) and 76*s.scale or 59*s.scale			
					else
						if not RB.overdrive --Drain Energy?
							local DRAIN = 11
							if (RB.boosting>70)
								DRAIN = (RB.boosting>140) and $-3 or (RB.boosting>105) and $-2 or $-1
							end
							if (p.rings>29) 
								DRAIN = (p.rings>119) and $-3 or (p.rings>59) and $-2 or $-1	
							end
							if p.powers[pw_sneakers]
								DRAIN = $/2
							end
							RB.boostmeter = max($-DRAIN,0)
						end
						if (s.eflags & MFE_UNDERWATER) and ((p.powers[pw_shield]&~SH_FIREFLOWER)!=SH_BUBBLEWRAP)
							TOPSPEED = 56*s.scale
							MINSPEED = 31*s.scale
						end
					end
					--Now thrust using GS.railspeed.
					if (TOPSPEED > RB.BoostSpeed) --Our top speed decreased!
						RB.BoostSpeed = max(TOPSPEED, $-(s.scale/4))
					elseif (TOPSPEED < RB.BoostSpeed) and (SPEED < RB.BoostSpeed)
						RB.BoostSpeed = max(TOPSPEED, $-(MINSPEED/100))
					end
					if (SPEED < RB.BoostSpeed)
						local MINBOOST = (RB.boosting < 4) and RB.BoostSpeed/2 or RB.BoostSpeed*2/3
						if (SPEED < MINBOOST) and GS.myrail
							GS.railspeed = MINBOOST*GS_RAILS.GrindFlip(s, GS.myrail, true) --Go at THIS minimum speed.
						end
						GS.railspeed = ($<0) and $-(MINSPEED/41) or $+(MINSPEED/41) --Now gradually accelerate.
						SPEED = abs(GS.railspeed)
					end
					yu.superaura,yu.superaurabuild = 0,0
					if (SPEED > 32*s.scale) --Speeding along smoothly!
						local RNG = P_RandomRange(6250,29250)
						if not ((GS.flipanim or 0)>2)
							YUSO.BoostAura(s, 80250+RNG)
						end
						RushGhost(s)
						GS.speedtrail,yu.alreadytrailed,yu.spintrailticket = true, false, nil					
					--	YUSO.YuSpinTrail(p, s, yu)		
						if s.YuBoostAura and not ((GS.flipanim or 0)>2) --Keep the Boost aura synced.
							local MAXZ,MINZ = P_RandomRange(60,74)*s.scale, P_RandomRange(1,-10)*s.scale
							local DIST,ZHEIGHT,XDIST,ghs = 0,0,18*s.scale			
							for d = 1,4
								ZHEIGHT = (d==1 or d==3) and MAXZ or MINZ
								DIST = ((d<3) and XDIST or -XDIST)+30*s.scale
								ghs = s.YuBoostAura[d]
								if ghs and ghs.valid
									local X,Y = FixedMul(DIST, cos(p.drawangle+ANGLE_90)),  FixedMul(DIST, sin(p.drawangle+ANGLE_90))
									P_MoveOrigin(ghs, s.x+(s.momx/3)+X, s.y+(s.momy/3)+Y, s.z+ZHEIGHT)
									if ghs.floorspriteslope
										ghs.floorspriteslope.o = {x = s.x, y = s.y, z = s.z+ZHEIGHT}
									end
								end
							end
						end 
					end
				--	print("RB.boosting:\x82"+RB.boosting+"\x80 RB.BoostSpeed:\x84"+RB.BoostSpeed/FRACUNIT+"\x80 GS.railspeed:\x88"+SPEED/FRACUNIT)
					
				else --Not holding SPIN, thus not boosting!
					if RB.boosting
						EndBoost(p,s,yu,RB) 
						RB.boosting,GS.YU_GhostUpKeep = 0,18
						GS.railc1,GS.railspin = 999<<16,999<<16
					end				
					if (abs(GS.railspeed) > 50*s.scale) and not (p.cmd.buttons & BT_TOSSFLAG)
						local ghs = RushGhost(s)
						ghs.dispoffset = 79
					end
				end				
			end	
		end end
		
		if GS.YU_GhostUpKeep
			GS.YU_GhostUpKeep = $-1
			if not GS.YU_GhostUpKeep
				if s.YuRushOverGhost and s.YuRushOverGhost.valid
					P_RemoveMobj(s.YuRushOverGhost)
					s.YuRushOverGhost = nil
				end
			elseif s.YuRushOverGhost and s.YuRushOverGhost.valid
				YUSO.RushOverLayGhost(p, s, yu, s.YuRushOverGhost.color or SKINCOLOR_SKY)
			end
		end			
		if yu.jumpball and yu.jumpball.valid
			yu.jumpball.angle = p.drawangle
			P_MoveOrigin(yu.jumpball, s.x, s.y, s.z)
		end
		if yu.myshield and yu.myshield.valid
			local t = YUSO.YuShieldThinker(yu.myshield)
			if t and t.valid t.colorized = true end
		end
		if yu.flair=="advance"
			if s.YU_AdvanceSuperFlash and s.YU_AdvanceSuperFlash.valid		
				YUSO.YuLSAglow(p, s, yu, 0, 0, 1, 1, true, true)
			end
		elseif p.powers[pw_invulnerability]
			local SHINE = s.yugoldenshine
			for i = 1,3 --HEY! Stay synced up, you weirdo!
				if i==2 SHINE = s.yugoldenshine2 elseif i==3 SHINE = s.yugoldenshine3 end
				if SHINE and SHINE.valid
					P_MoveOrigin(SHINE, s.x, s.y, SHINE.z)
				end
			end
		end		
	end
end 

--If your character is spawning a speedtrail, this hook plays.
--It lets you manually input speedtrail variables to change up how your speedtrail looks.
--If this returns true, you won't spawn a speedtrail after all.
--Otherwise, everything that's nil is a default variable. It's a bit of a niche, advanced hook!
SKIN["SpeedTrailVariables"] = function(p, s, GS)
	local yu = p.yusonictable if not (yu) or (yu.alreadytrailed) return true end --	
	local BLEND,COLOR,FUSE,SCALESPEED,DENSITY,FRAME,SPRITE
	local BOOSTING = (p.YuRushMode and p.YuRushMode.boosting) and true or false	
	
	COLOR = (p.powers[pw_super] and (p.rings<10)) and SKINCOLOR_CRIMSON or YuAuraColor(s, nil, yu, true)	
	
	if BOOSTING==true
		if COLOR == SKINCOLOR_SADXSUPERSONIC or COLOR == SKINCOLOR_ADVANCESUPERSONIC
			FRAME = 44|FF_TRANS90
		else
			DENSITY = 11*s.scale
		end
	else
		if yu.flair=="advance" return true end --
		if (COLOR>=SKINCOLOR_SABLUETRAIL) and (COLOR<=SKINCOLOR_SAREDTRAIL) 
		or (COLOR>=SKINCOLOR_RUBY) and (COLOR<=SKINCOLOR_CRIMSON)
			DENSITY = 6*s.scale
		end
	end
	if (COLOR>=SKINCOLOR_SABLUETRAIL and COLOR<=SKINCOLOR_YULSA)
	or p.powers[pw_super] or yu.customtrailshiny
		BLEND = AST_ADD
	else
		BLEND = AST_TRANSLUCENT
	end		
	yu.alreadytrailed = true
	
	return BLEND,COLOR,FUSE,SCALESPEED,DENSITY,FRAME --
end